package model;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * A class representing shared characteristics of animals.
 * 
 * @author David J. Barnes and Michael Kölling
 * @version 2011.07.31
 */
public abstract class Animal implements Actor
{
    // Whether the animal is alive or not.
    private boolean alive;
    // The animal's field.
    protected Field field;
    // The animal's position in the field.
    private Location location;
    // The animal's age
    private int age;
    //The animal's food level
    protected int foodLevel;
    //make random
    private static final Random rand = Randomizer.getRandom();    
    // History of animals put onto the field
    private HashMap<String, Integer> history;
    // ziekte
    private boolean sick;
    private int sickCounter;

    public Animal(){}
    
    /**
     * Create a new animal at location in field.
     * 
     * @param field The field currently occupied.
     * @param location The location within the field.
     */
    public Animal(Field field, Location location)
    {
        alive = true;
        this.field = field;
        setLocation(location);
        age = 0;
        setSick(false);
        sickCounter = 0;
    }
    
    /**
     * Returns the number of steps an animal has left to find food 
     * if this reaches zero to animal will die of starvation
     * @return int foodLevel
     */
    
    public int getFoodLevel() {
    	return foodLevel;
    }
    
    /**
     * Sets the amount of steps an animal has left before starving
     * @param foodLevel
     */
    
    public void setFoodLevel(int foodLevel) {
    	this.foodLevel = foodLevel;
    }
    
    /**
     * Make this animal act - that is: make it do
     * whatever it wants/needs to do.
     * @param newAnimals A list to receive newly born animals.
     */
    public void act(List<Actor> newAnimals) {
        incrementAge();
        incrementHunger();
        if(isAlive()){
        	move();
        }
    }

    /**
     * Method to make an animal move to a new location on the field. 
     * The method first tries to find food in an adjacent tile, if this is found it moves to this location
     * if no food is found it searches for a free adjacent tile, if this is not found either the animal dies
     * of overcrowding. When the animal lands on a road modifier there is a chance it will be hit by a car.
     * @return void
     **/
    
    protected void move() {
        // Move towards a source of food if found.
        Location newLocation = findFood();
        if(newLocation == null) { 
            // No food found - try to move to a free location.
            newLocation = getField().freeAdjacentLocation(getLocation());
        }
        // See if it was possible to move.
        if(newLocation != null) {
        	// If animal moves on road it might get hit by a car
        	if(field.getModifierAt(newLocation) instanceof ModifierRoad && rand.nextDouble() <= ModifierRoad.getTrafficLevel()) {
        		// Poor animal got hit	
        		setDead();
        		System.out.println("Animal done goofed, why you do this animal?");
        		}
        		else {
        			setLocation(newLocation);
        		}
        }
        else {
            // Overcrowding.
            setDead();
        }
    }
    
    /**
     * Look for food adjacent to the current location.
     * Only the first grass is eaten.
     * @return Where food was found, or null if it wasn't.
     */
    protected abstract Location findFood();
    
    /**
     * Check whether the animal is alive or not.
     * @return true if the animal is still alive.
     */
    protected boolean isAlive()
    {
        return alive;
    }

    /**
     * Check whether the animal is alive or not.
     * @return true if the animal is still alive.
     */
    public boolean isActive()
    {
        return alive;
    }
    
    /**
     * Indicate that the animal is no longer alive.
     * It is removed from the field.
     */
    protected void setDead()
    {
        alive = false;
        if(location != null) {
            field.clear(location);
            location = null;
            field = null;
        }
    }

    /**
     * Return the animal's location.
     * @return The animal's location.
     */
    protected Location getLocation()
    {
        return location;
    }
    
    /**
     * Place the animal at the new location in the given field.
     * @param newLocation The animal's new location.
     */
    protected void setLocation(Location newLocation)
    {
        if(location != null) {
            field.clear(location);
        }
        location = newLocation;
        field.place(this, newLocation);
    }
    
    /**
     * Return the animal's field.
     * @return The animal's field.
     */
    protected Field getField()
    {
        return field;
    }
    
    /**
     * Get the current age of the animal
     * @return the current age of the animal
     */
    public int getAge(){
    	return age;
    }
    
    /**
     * Set the age of the animal
     * @param age of the animal
     */
    public void setAge(int age){
    	this.age = age;
    }
    
    /**
     * Increase the age.
     * This could result in the animal's death.
     */
    protected void incrementAge()
    {
        age++;
        if(age > getMaxAge()) {
            setDead();
        }
    }
    
    /**
     * Make this rabbit more hungry. This could result in the rabbit's death.
     */
    protected void incrementHunger()
    {
        foodLevel--;
        if(foodLevel <= 0) {
            setDead();
        }
    }
    
    /**
     * Generate a number representing the number of births,
     * if it can breed.
     * @return The number of births (may be zero).
     */
    protected int breed()
    {
        int births = 0;
        if(canBreed() && rand.nextDouble() <= getBreedingProbability()) {
            births = rand.nextInt(getMaxLitterSize()) + 1;
        }
        return births;
    }
    
    /**
     * A fox can breed if it has reached the breeding age.
     */
    protected boolean canBreed()
    {
        return age >= getBreedingAge();
    }

    /**
     * Check whether or not this fox is to give birth at this step.
     * New births will be made into free adjacent locations.
     * @param newFoxes A list to return newly born foxes.
     */
    protected void giveBirth(List<Actor> newFoxes)
    {
        // New foxes are born into adjacent locations.
        // Get a list of adjacent free locations.
        Field field = getField();
        List<Location> free = field.getFreeAdjacentLocations(getLocation());
        int births = breed();
        for(int b = 0; b < births && free.size() > 0; b++) {
            Location loc = free.remove(0);
            Animal young = newYoung(loc);
            newFoxes.add(young);
        }
    }
    
    /**
     * Methods that returns true when an animal is sick, false otherwise
     * @return boolean if animal is sick
     */
    
    public boolean getSick() {
		return sick;
	}

    /**
     * Methods that makes an animal sick if a boolean true is given
     * @param boolean sick set animal as sick
     */
    
	public void setSick(boolean sick) {
		this.sick = sick;
	}
    
    /**
     * Get the maximum age of the animal
     * @return maximum age
     */
    public abstract int getMaxAge();
    
    public abstract void setMaxAge(int maxAge);
    
    /**
     * Get the breeding age of the animal
     * @return breeding age
     */
    public abstract int getBreedingAge();
    
    /**
     * set the age when an animal can start breeding
     * @param int breedingAge
     */
    public abstract void setBreedingAge(int breedingAge);
    
    /**
     * Return the probabilty the animal will reproduce as a double
     * @return double breeding probability
     */
    public abstract double getBreedingProbability();
    
    /**
     * Set the probability that the animal will reproduce during a step of the simulation
     * @param breedingProbability
     */
    public abstract void setBreedingProbability(double breedingProbability);
    
    /**
     * Returns the maximum number of offspring an animal can have in one litter
     * @return int max litter size
     */
    public abstract int getMaxLitterSize();
    
    /**
     * Set the maximum number of offspring an animal can have in one litter 
     * @param int maxLitterSize
     */
    public abstract void setMaxLitterSize(int maxLitterSize);
    
    
    abstract Animal newYoung(Location location);

    /**
     * Returns the number of steps an animal has been sick
     * @return int number of turns since infection
     */
	public int getSickCounter() {
		return sickCounter;
	}

	/**
	 * increment the number of steps since infection
	 * @return void
	 */
	public void incrementSickCounter() {
		sickCounter++;
	}
	
	/**
	 * Sets the number of turns since infection
	 * @param int sickCounter
	 */
	public void setSickCounter(int sickCounter) {
		this.sickCounter = sickCounter;
	}
	
	/**
	 * Returns a textual representation of the animal
	 */
	public abstract String toString();

	
}
